The Digital Signature Demo illustrates how AOCE Digital Signature technology may be incorporated into an existing application with a minimum of fuss, and without significantly perturbation of the existing application.
The demo extends the Think Class Library “Tiny Edit” sample program, allowing documents created by TinyEdit to be “signed” and “verified” by the Digital Signature Manager. It also shows how Digital Signatures may be applied to application data, such as the state of checkboxes and radio buttons.
Although the class is defined for, and operates within the Think Class Library organizational structure, it should be relatively easy to adapt it to other class hierarchies. Even if you do not use a class library design in your own applications, you should still be able to use the demo as a basis for your own Digital Signature subroutine library.
Class Hierarchy
The demo defines a CSignature class and two subclasses that are used to manipulate digital signatures. It also adds a few simple classes that add the necessary capabilities to TinyEdit. The class hierarchy is as follows:
Class Description
CSignature The base class for Digital Signatures. CSignature is a subclass to the base-class, CObject. Applications normally do not create instances of this class, but rather use subclasses.
CSignedDataFile This class allows signing or verifying an entire file. It is used to sign a document after it has been saved to disk, and to verify an existing signed document.
CSignedObject This class allows signing or verifying a data object. Your application should create a subclass with the necessary content and methods. You can then use CSignedObject methods to sign or verify the object’s actual data.
Sample Program
The sample program is an extension of the Think Class Library “TinyEdit” sample application. It extends the CEditDoc class to provide for signed and verified text files and adds a sample class to show how you can sign and verify the contents of a dialog. The demo requires the following source files and their headers:
File Description
DigitalSignatureDemo.c This is the main program. It creates a DemoApp class.
DemoApp.c This is a subclass to CEditApp that lets you open signed documents and starts the signed object demo. It also overrides a “shutdown” method to ensure that the signature context has been removed when your application exits.
DemoEditDoc.c This is a subclass to CEditDoc that lets you sign and verify documents. It also has methods override CEditDoc methods in order to provide better error messages.
DemoSignedObject.c This object can sign and verify its data contents. It is a subclass to CSignedObject.
DemoSignedObjectDialog.c This dialog manages a DemoSignedObject instance. It displays a dialog that lets you change checkbox and text items, and controls signing, verifying, and other CSignedObject functions. It is a subclass to CDLOGDirector. Although it appears to the user as a modal dialog, it is actually implemented as a normal window.
ErrorMessage.c This file contains two simple display functions for debugging convenience. Error messages were included within the program text so there was a single reference point that explains errors. A production program should preferably store its error messages in resource files
SIGStatusManager.c This contains the status window manager, window, and window contents. This window is displayed when you specify the built-in status window for a Sign or Verify operation.
SystemSupportsAOCE.c This subroutine tests for the presence of the AOCE toolbox. It returns an error if the toolbox was not installed or was disabled.
Potential Problems
There are a few things that application developers need to be aware of when using Digital Signatures (these are independent of the example):
• When your application exits, be sure that you have disposed of the signing context (SIGContextPtr datum). In general, this means that your application should patch the ExitToShell trap with a small subroutine that checks for an active context. The sample program stores the context pointer in a global variable, and overrides CApplication::RemovePatches to check that it has been deleted when the application exits.
• Signing and verifying take a fairly long time; your application should use a status procedure to entertain the user while signing. This entails a bit of effort: you should not initiate signing or verifying from a Modal Dialog, but should use some form of “standard” window or a non-modal dialog. In the example, the signed object demo uses a subclass of TCL’s CDialog class, which implements a modal-dialog lookalike using the normal window-based event loop. The reason for this is that your status callback should call your normal event loop: if you have an active modal dialog, it will not receive update events properly.
• When a file is signed, it is set “locked,” i.e., read-only This means that your application must open it read-only (fsRdPerm). Also, your users cannot create a new file with the same name without first, manually, renaming the file or placing it into the trash.
• The method descriptions only describe the errors that your application needs to handle (because they’re benign or expected). However, note that there are a number of other Digital Signature Manager errors that may also occur. You should carefully test your application to make sure it displays informative error messages in all circumstances.
There are a number of instances where errors should be detected in your application. For example, signed files are read-only (“locked”) and must be opened with permission set to fsRdPerm, rather than the standard fsRdWrPerm. You should search the demo for Message() calls to see those errors I found worth detecting.
The remainder of this document describes the individual classes.
CSignature
Introduction
CSignature is the base class for Digital Signature processing. It contains methods that all Digital Signature processing requires, as well as a few utility classes. Your application does not use CSignature directly, but rather uses one of its sub-classes.
Heritage
Superclass CObject
Subclasses CSignedDataFile, CSignedObject
Global Variables
Variable Type Description
gSIGStatusProc SIGStatusProcPtr Default status procedure: pass this to Sign, SignFile, etc. to use the built-in procedure.
gSIGContextPtr SIGContextPtr The Signer Manager context. Be sure that your application disposes of any context before it terminates.
Global Functions
void DisposeSignerContext(void);
Dispose of the signer context, if it exists. This must be called when your application exits (you should trap ExitToShell so you call DisposeSignerContext even if your application crashes).
Instance Variables
Variable Type Description
itsContextType unsigned long This is the type of the context variable: it is needed only by methods that enable/disable the ShowSigner option as that option is valid only after verifying an object or file.
itsStatusManager SIGStatusManager An instance of this class is created if you specify the default status window.
Methods
Construction and destruction methods
void ISignature(void);
Initialize the private variables.
void Dispose(void);
Delete any context and signature and dispose of the CSignature object.
Signer Context Methods
void NewContext(
unsigned long contextType
);
Create a new context of the indicated type. The contextType parameter may be one of the following values, which are defined in <DigitalSignature.h>
kSIGSign Signing a file or object
kSIGVerify Verifying a file or object
kSIGDigest Preparing a digest of an object
void CheckForContext(void);
Fail if there is no current context with a kSIGContextPrepareErr. This would indicate a programming error where one of the routines that requires a context was called when there was no context. For example, calling Sign when your application hasn’t called SignPrepare, would cause this failure.
unsigned long GetContextType(void);
Return zero if there is no current context, else the type that was specified when the application called NewSignerContext.
Signature Methods
Size SignPrepare(
const FSSpec *signerFile,
ConstStr255Param *prompt
);
SignPrepare prepares the Signer Manager to sign a document or data. If signerFile is not NULL, it will be used to define the signer file, while if NULL, the toolbox will prompt for the user’s signer file. Prompt is used to prompt the user for a signer file, if "\p", the default prompt will be used. SignPrepare returns the size of the Signature record.
SignPrepare our application should use a TRY/CATCH context to detect the following errors:
kSigUserCanceled The user cancelled this signer request. You may choose to ignore this error.
kSIGPasswordErr The user did not enter the proper password for the designated signer file.
kSIGSignerNotValid The signer file is not valid; perhaps it has expired.
void ShowSigner(void);
After your application has successfully verified a file or object, ShowSigner may be used to display the entire distinguished name of the object’s or file’s signer. Your TRY/CATCH context should catch the following error:
kSIGOperationIncompatibleErr ShowSigner is valid only after verification; it cannot be used after signing an object or file. See the signed object demo for an example of how a button that controls this request may be enabled or disabled as needed.
void GetSignerInfo(
SIGSignerInfo *signerInfo
);
After successfully signing or verifying an object or file, you can call this method to copy information about the signer to your buffer. The buffer should be in locked memory.
Boolean GetCertInfo(
unsigned long certIndex,
SIGCertInfo *certInfo
);
GetCertInfo can be used to iterate through the certificates that make up a signature. It returns TRUE if it finds a certificate for this index, FALSE if the index is out of range, and fails on other errors. It may be called only after successfully signing or verifying an object or file.
Boolean GetCertNameAttributes(
unsigned long certIndex,
unsigned long attributeIndex,
SIGNameAttributesInfo *attributeInfo
);
GetCertInfo can be used to iterate through the attributes for a certificate. It returns TRUE if successful, FALSE if the certIndex is out of range, and raises an error condition on other errors. It may be called only after successfully signing or verifying an object or file.
Protected Methods
void InitDefaultStatusProc(
ConstStr255Param actionString,
ConstStr255Param objectString
);
This is called if you pass gSIGStatusProc to SignFile, VerifyFile, etc. It creates a SIGStatusManager object which displays the status window.
void DisposeDefaultStatusProc(void)
Dispose of the status window.
SIGStatusManager
Introduction
SIGStatusManager manages the status window that is displayed when an application selects the default status procedure. It is created and managed by the CSignature instance: your application will normally not be concerned with it.
Heritage
Superclass CDirector
Subclasses none
Instance Variables
Variable Type Description
itsTickCount unsigned long This is used to time the “barber pole” progress indicator.
Static Variables
Variable Type Description
gContinueSigning Boolean This is set TRUE when signing begins, and is set FALSE if the user clicks on the Cancel button. The value is passed to the Digital Signature Manager by the default status procedure.
Methods
Construction and destruction methods
void ISIGStatusManager(
ConstStr255Param actionString,
ConstStr255Param objectString
);
Create the status window (this uses some private classes that are defined in SIGStatusManager.c). The actionString and objectString are concatenated and displayed in the window. These might typically be “Signing “ “Signed File.”
Operational methods
void DoCommand(
long aCommand
);
The status window has a cancel button: look for it and take appropriate action by setting gContinueSigning FALSE and passing the cancel command to the manager’s superclass.
void Dawdle(
long *maxSleep
);
TCL calls this repeatedly to manage the progress indicator.
CSignedObject
Introduction
CSignedObject lets you sign and verify the contents of an individual data object, such as a spreadsheet value or the position of an object in a drawing application. You can then store and recover the signature record as part of your application’s normal storage process.
Heritage
Superclass CSignature
Subclasses none
Instance Variables
Variable Type Description
itsSignature SIGSignaturePtr The signature record, if any.
itsSignatureSize Size The size of the signature record.
Using CSignedObject
Your application should create a subclass of CSignedObject for all objects that need data-specific signature processing.
Methods
Except as shown, all methods use the Failure mechanism (as described in <Exceptions.h> to report errors. In most cases, this means that your application must establish a TRY/CATCH context when calling these methods:
kSIGUserCanceled The user cancelled this sign or verify request. You may choose to ignore this error.
kSIGPasswordErr The user entered an incorrect password.
kSIGSignerNotValidErr The signer file is not valid; perhaps it has expired.
kSIGNoSignature Programming error: your application called Sign or Verify without having called SignPrepare or VerifyPrepare.
kSIGVerifyFailedErr Verification failed.
kSIGInvalidCredentialErr Verification succeeded, but credentials are invalid; perhaps they have expired.
kSigSignerErr Some sort of error with the signer: the signature is not valid.
Construction and destruction methods
void ISignedObject(void);
Initialize the private variables.
void Dispose(void);
Delete any context and signature and dispose of the CSignature object.
Signing and Verifying Methods
Size SignPrepare(
const FSSpec *signerFile,
ConstStr255Param *prompt
);
Override CSignature::SignPrepare to store the signature size.
void Sign(
SIGStatusProcPtr statusProc
);
Sign completes the digital signature process. To sign an object, you should call SignPrepare, ProcessData, and then Sign. The statusProc, if not NULL, may be used to inform the user that a signature operation is occurring. Errors are reported using the Failure mechanism.
void VerifyPrepare(
SIGStatusProcPtr statusProc
);
Begin the process of verifying a digital signature process. To verify an object, you should call VerifyPrepare, ProcessData, then Verify. Errors are reported using the Failure mechanism.
void Verify(void);
Verify completes the digital signature verification. Errors are reported using the Failure mechanism.
void ProcessData(
const void *dataPtr,
Size dataSize
);
Compute or verify a Digital Signature. This is called for all data elements.
Signature Methods
Size GetSignatureSize(void);
Return the size of the signature record, as specified—originally—by SignPrepare.
void NewSignature(void);
Create a signature record using the currently-specified size.
void DisposeSignature(void);
Dispose of the current signature buffer, if any, but do not dispose of the context.
Boolean HasSignature(void);
Return TRUE if there is a current signature.
void CheckForSignature(void);
Fail with an error (kSIGNoSignature) if the application hasn’t established a signature record.
void CopySignatureToUserBuffer(
void *buffer,
Size bufferSize
);
Copy the current signature to the user’s buffer. BufferSize is the size of the user’s buffer; a paramErr error condition will be raised if bufferSize cannot hold the entire signature.
void MakeSignatureFromUserBuffer(
void *buffer,
Size bufferSize
);
Create a signature record using the information in the user’s buffer. CopySignatureToUserBuffer and MakeSignatureFromUserBuffer may be used together to save and restore object signatures.
void ReadSignature(
CDataFile *aFile
);
Read a signature record from the indicated file. If a non-null signature was read, create it from the given data. The actual data includes a header record that indicates that the data is a signature and specifies its length.
void WriteSignature(
CDataFile *aFile
);
Write a signature to an open file. This might be called as part of your “save this object” process. aFile is a TCL data file. This also writes a header record with the signature length: if it is called when there no signature record, it will write a zero-length record that ReadSignature correctly interprets.
CSignedDataFile
Introduction
CSignedDataFile lets you sign and verify the contents of an entire file.
Heritage
Superclass CSignature
Subclasses none
Using CSignedDataFile
Use this as part of your application’s Open and Close document processing.
Methods
Except as shown, all methods use the Failure mechanism (as described in <Exceptions.h> to report errors. In certain cases, this means that your application must establish a TRY/CATCH context when calling these methods.
Construction and destruction methods
void ISignedDataFile(void);
Initalize the object instance.
void SignFile(
const FSSpec *signerFile,
ConstStr255Param promptString,
const FSSpec *dataFile,
SIGStatusProcPtr statusProc
);
Sign the file specified by dataFile. If signerFile is not NULL, it will be used to define the signer file, while if NULL, the toolbox will prompt for the user’s signer file. Prompt is used to prompt the user for a signer file, if "\p", the default prompt will be used. SignPrepare returns the size of the Signature record. StatusProc defines the user status procedure. Use gSIGStatusProc for the default window.
Because of the way the Think Class Library closes files, you should call SignFile in the following sequence:
1 Create a copy of the FSSpec associated with the file.
2 Write all data and close the file. You can delete the CDataFile object at this time.
3 SignFile using the saved FSSpec.
4 Call DisposeSignerContext
You should call SignPrepare…SignFile in a TRY/CATCH context, checking for the following errors:
kSIGUserCanceled The user cancelled this signer request. You may choose to ignore this error.
kSIGPasswordErr The user entered an incorrect password.
kSIGSignerNotValid The signer file is incorrect.
wrPermErr The data file is marked read-only (perhaps it was already signed?)
void VerifyFile(
const FSSpec *dataFile,
SIGStatusProcPtr statusProc
);
Verify the signature in the file specified by dataFile. StatusProc defines the user status procedure. Use gSIGStatusProc for the default window. You should call VerifyFile in a TRY/CATCH context, checking for the following errors:
kSIGUserCanceled The user cancelled this verification request. You may choose to ignore this error.
kSIGVerifyFailedErr Verification failed: the file or signature may have been changed.
kSIGInvalidCredentialErr Verification succeeded, but the credentials are pending or have expired. The context is still active.
Boolean FileIsSigned(
const FSSpec *dataFile,
);
Return TRUE if the specified file has a signature. Note that the signature is not verified.